Skip to content

(Thrift Field Masker Pt2) Implement field masking for thrift - #6311

Merged
jrhee17 merged 11 commits into
line:mainfrom
jrhee17:feat/thrift-log-maskers-tproto-part2
Jul 28, 2025
Merged

(Thrift Field Masker Pt2) Implement field masking for thrift#6311
jrhee17 merged 11 commits into
line:mainfrom
jrhee17:feat/thrift-log-maskers-tproto-part2

Conversation

@jrhee17

@jrhee17 jrhee17 commented Jul 10, 2025

Copy link
Copy Markdown
Contributor

Motivation:

Following field maskers for annotated services at #6232 , this PR attempts to introduce FieldMasker support for thrift.

To ensure that deserialization is also supported natively using TProtocol, the design of thrift uses a variant of TProtocol which keeps track of the field of a specified instance.
Unmasking support is to be done in the ensuing PR for easier reviews.

Modifications:

  • Introduce RpcRequestSerializer, RpcResponseSerializer to serialize rpc requests
  • ThriftFieldInfo, ThriftFieldMaskerSelector, and ThriftFieldMaskerSelectorProvider are implemented to support the FieldMasker API.
  • TBaseSelectorCache, TBaseSerializer, MaskingTProtocol are implemented to actually apply field masking during serialization
  • Added the ability to specify the annotations_as_metadata option to the gradle scripts

Result:

  • Users can use the FieldMasker API for thrift services

@jrhee17 jrhee17 added this to the 1.33.0 milestone Jul 10, 2025
@jrhee17
jrhee17 marked this pull request as ready for review July 16, 2025 01:19
@jrhee17

jrhee17 commented Jul 16, 2025

Copy link
Copy Markdown
Contributor Author

ready for review


private static ContentSanitizer<String> commonContentSanitizer() {
return ContentSanitizer.builder()
.fieldMaskerSelector(ThriftFieldMaskerSelector.of(info -> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of adding a builder to create a ThriftFieldMaskerSelector in a declarative way for simple cases?

For example:

ThriftFieldMaskerSelector
   .buider()
   .onFieldAnnotation("secret", FieldMasker.nullify())
   .onFieldAnnotation("grade", "red", new CustomFieldMasker())
   ...
   .build();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a variant of ThriftFieldMaskerSelector to thrift0.19> since that's when annotations were supported.
Note that this the variant still requires that the thrift idl is compiled with the annotations_as_metadata option

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks nice. Should we add a integration test case using ThriftFieldMaskerSelectorBuilder as an example so that users can reference it?

@minwoox minwoox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good all in all. 👍

Comment thread thrift/thrift0.13/src/test/thrift/main.thrift Outdated

@minwoox minwoox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good all in all. 👍

@minwoox minwoox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. 👍

@ikhoon ikhoon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @jrhee17! 🙇‍♂️🚀

import com.linecorp.armeria.common.logging.FieldMasker;

/**
* Holds information about a thrift struct field.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Holds information about a thrift struct field.
* Holds information about a Thrift struct field.

@Override
public FieldMasker fieldMasker(ThriftFieldInfo info) {
for (AnnotationAndMasker rule : rules) {
if (rule.matches(info.fieldMetaData().getFieldAnnotations())) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we extract info.fieldMetaData().getFieldAnnotations() into a local variable since getFieldAnnotations() may create UnmodifiableMap for each invocation.

// Copied from the generated `SecretStruct`
new org.apache.thrift.meta_data.FieldMetaData("secret", org.apache.thrift.TFieldRequirementType.DEFAULT, 
        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING), 
        java.util.stream.Stream.<java.util.Map.Entry<java.lang.String, java.lang.String>>builder()
            .add(new java.util.AbstractMap.SimpleImmutableEntry<>("grade", "red"))
            .build().collect(java.util.stream.Collectors.toMap(java.util.Map.Entry::getKey, java.util.Map.Entry::getValue))));


private static ContentSanitizer<String> commonContentSanitizer() {
return ContentSanitizer.builder()
.fieldMaskerSelector(ThriftFieldMaskerSelector.of(info -> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks nice. Should we add a integration test case using ThriftFieldMaskerSelectorBuilder as an example so that users can reference it?

@jrhee17
jrhee17 merged commit 883e8bf into line:main Jul 28, 2025
11 of 14 checks passed
@codecov

codecov Bot commented Jul 28, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (8150425) to head (67a0386).
⚠️ Report is 126 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main   #6311       +/-   ##
============================================
- Coverage     74.46%       0   -74.47%     
============================================
  Files          1963       0     -1963     
  Lines         82437       0    -82437     
  Branches      10764       0    -10764     
============================================
- Hits          61385       0    -61385     
+ Misses        15918       0    -15918     
+ Partials       5134       0     -5134     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

ikhoon pushed a commit that referenced this pull request Aug 4, 2025
Motivation:

Following the implementation of
#6311, this changeset attempts to
implement deserialization of masked fields.

Doing so would allow users to define a `FieldMasker` as follows:
```
FieldMasker.builder()
  .addMasker(
    NormalFooStruct.class,
    struct -> encryptToString(struct),
    str -> return decryptToStruct(str));
```

Conceptually, this implementation is a corollary of
`MaskingBeanDeserializerModifier`.
There is no change in the public API as
`FieldMaskerBuilder#addMasker(Class, Function, Function)` is already
exposed.

Modifications:

- Added `UnMaskingTProtocol`, `TMaskingDeserializer`,
`UnMaskingContexts` which helps with the implementation of unmasking
- Deserialization requires instantiation of the default instance. To aid
with this, `TBaseCache` has been introduced.

Result:

- The unmasking API functions correctly for thrift structs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants